Pointer sind mit das schwierigste Feature der Programmiersprache C, und es gibt hier (meiner Ansicht nach) einigen Erklärungsbedarf.

Was ist ein Pointer? (Black-Box - Erklärung)

Verwendung von Variablen (Prinzip)Abbildung 1: Verwendung einer Variablen

In den meisten Lehrveranstaltungen werden Variablen bildlich mit einem imaginären benannten Platzhalter (wir sagen statt Es ist jetzt 14:00 Uhr für ein Programm, dass das Datum ausgeben soll, jetzt Es ist jetzt dieUhrzeit Uhr, wobei dieUhrzeit durch die aktuelle Uhrzeit ersetzt werden soll) oder vielleicht einer gedachten Box, die eine Zahl oder andere abstrakte Dinge wie Zeichen enthalten kann, und auf der ein Name - der Name der Variablen - steht.

In Wirklichkeit handelt es sich bei der Box um eine oder auch mehrere Speicherzellen des Computers, der Name der Variablen wird nur benötigt, damit das Programm für den Computer so übersetzt werden kann, dass eben diese Speicherzelle auch benutzt wird, wenn der Quelltext dies verlangt. Der eigentliche Name einer Variablen existiert nach der Übersetzung in das Programm gar nicht mehr! Es gibt allerdings (exotische, daher hierfür irrelevante) Ausnahmen.

Eine einfache Erweiterung dieses Modelldenkens sind Pointer: man vereinbart - ähnlich des gedachten Mechanismus der Benennung einer Variablen, z.B. als "dieUhrzeit" - eine Variable, die einen "Verweis" oder auch "Hinweis" auf eine Variable enthalten oder komplett leer sein kann. Dies ist ein wesentlicher Unterschied zu einer Kopie!
Eine Pointervariable sollte man nicht als eine Box verstehen, die die Box mit der Uhrzeit enthält, vielmehr ist eine Box, die einen Hinweis enthält, wo denn die Box der Variablen zu finden ist, ein korrektes Modell. (Oder auch eine Box mit einer sozusagen verschränkten Kopie der Box mit der Variablen, allerdings mit gleichen, veränderbaren Eigenschaften, die sich dann auf die jeweils andere Box auswirken und der Möglichkeit unendlich vieler Kopien)

Praxis

In der Praxis wird dieses Prinzip nun wie folgt implementiert:

Variablen sind wie besprochen bestimmte Speicherzellen des Computers, die Daten eines bestimmten Types (Zahlen, Buchstaben, ...) aufnehmen können. Während Daten oft nur in Zusammenhang mit der Information, welchen Types sie sind Sinn machen, speichern Computer sie (bis heute) immer gleich: physikalisch, und zwar als Bits. Während es unzählige Arten von Speicher gibt, muss man sich auf eine bestimmte Art und Weise einigen, anzugeben, wo denn die gesuchten Bits nun auf dem Speichermedium zu finden sind: Adressen. Es gibt im Vergleich zu Postadressen allerdings einige Besonderheiten: Speicheradressen sind ausschließlich Zahlen. Es wird dabei oft mit der Nummerierung bei 0 begonnen und diese fortlaufend für Gruppen von je 8 Bits um eins erhöht. Beispiel:

Adresse    | Daten, Darstellung als Bits  | Daten, Interpretation als Zahl über 8 Bit
0          | 01011011                     | 91
1          | 00001100                     | 12
...
4294967295 | 00000001                     | 1

Direkt adressieren kann man also nur die Speicherstellen 0 bis 4294967295 (hier als Beispiel), nichts dazwischen.

In C haben die Variablen der üblichen Datentypen (int, char, float, byte) jedoch teils weit mehr als nur 1 Byte assoziierten Speicher. Eine Variable vom Typus int ist beispielsweise heute meist 4 Byte groß. Beispiel: Sei int meineVariable = 2581234;

evtl. Variable  | Adresse    | Daten, Darstellung als Bits 
meineVariable   | 127884592  | 00000000
                | 127884593  | 00100111
                | 127884594  | 01100010
                | 127884595  | 11110010

Achtung: dieses Beispiel ist so nicht ganz korrekt! Weil die meisten heute verbreiteten PC - CPUs die Little Endian Bytereihenfolge verwenden, würden die Daten tatsächlich genau in umgekehrter Reihenfolge im Speicher stehen (also 11110010 bei 127884592, 01100010 bei 127884593, usw.). Dieser Umstand ist für uns aber hier irrelevant.

Ein Pointer auf meineVariable, int *pMeineVariable = &meineVariable; ist in Wirklichkeit nun eine Variable, deren Inhalt eine Adresse ist, bei der eine Variable vom Typus int steht. Beispiel:

evtl. Variable  | Adresse    | Daten, Darstellung als Bits 
meineVariable   | 127884592  | 00000000
                | 127884593  | 00100111
                | 127884594  | 01100010
                | 127884595  | 11110010
...
pMeineVariable  | 234234252  | 00000111
                | 234234253  | 10011111
                | 234234254  | 01011101
                | 234234255  | 00110000

im pMeineVariable steht also einfach nur die (32 Bit = 8 Byte) - Adresse von meineVariable.

Es gibt aber nicht nur Pointer auf Daten, sondern auch solche auf Funktionen. Man verwendet in diesem Zusammenhang gerne den Begriff Callback für einen Funktionsparamter, der ein Zeiger auf eine Funktion ist.

Pointer in der (C-) Praxis

Deklaration einer Pointer - Variablen

int *pMeineVariable;
Schema: Typ *Name;

Der Stern (*) gibt hier an, dass es sich um einen Zeiger auf handelt.
Achtung: das * gehört immer nur zum nächstliegendsten Namen, nicht zum Typ. Beispiel:

int *blah, bluh;

ist das selbe wie:

int *blah;
int bluh;

Das * gehört hier also nur zu einem Variablennamen!!!


Wie man komplizierte C-Deklarationen auf Anhieb korrekt liest: Regel von Friedl (Steve Friedl, Microsoft MVP.)

Kurzzusammenfassung: Vom Namen aus immer so weit nach rechts, bis du bei einer Klammer anstehst, dann soweit wie notwendig nach rechts und wieder nach links.

Referenzieren

... aka. die Adresse von Variablen (und Funktionen) holen.
Der & Operator holt die Adresse dessen, was auf seiner rechten Seite steht.

Eine der wichtigsten Fallen, die es in C zu verstehen gilt, ist der Unterschied zwischen der Deklarationssyntax und der Syntax im Programmablauf!

Beispiele:

int a = 0;
int *b = &a;

ist das selbe wie:

int a = 0;
int *b;

b = &a;

Der * hat in einer Deklaration nicht die selbe Bedeutung wie in einer Zuweisung!!!

Dereferenzieren

... aka. Zugreifen auf den Inhalt der Variable, auf die gezeigt wird.

Der * - Operator hat, außerhalb der Deklaration genau diese Wirkung. Man kann also einfach "*zeiger" anstatt einer gewöhnlichen Variablen schreiben. Beispiele:

int a = 11;
int *b;
int c = 3;

b = &a;
c = *b:
// c == 11 jetzt
*b = 2;
// a == 2 jetzt

Pointer - Arithmetik / Arrays

Versucht man, mit einem Pointer zu rechnen (insbesondere ihn zu erhöhen / verringern), passiert dies unter Beachtung der Größe des ihm zugrunde liegenden Types! Folgendes ist äquivalent, in C ist der Übergang zwischen Arrays und Pointern fließend:

int a[3];
int *b;
int *c;

a[0] = 22;
a[1] = 54;
a[2] = 47;

b = a;
c = &a[1];

ist das selbe wie:

int a[3];
int *b;
int *c;

a[0] = 22;
a[1] = 54;
a[2] = 47;

b = a; // <-- gleich wie: &a[0], aber weniger tipparbeit!
c = *(a + 1); // Achtung: a + 1 ergibt hier a + 1 * sizeof(int)!